package schedulo.core.task.reminder;

import java.io.IOException;
import java.util.Calendar;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;

import schedulo.core.task.Task;
import schedulo.core.task.provider.TaskProvider;
import schedulo.core.task.provider.TaskProviderFactory;
import schedulo.core.task.provider.TaskProviderFactoryListener;
import schedulo.core.task.provider.TaskProviderListener;

public class Reminder implements TaskProviderFactoryListener,
		TaskProviderListener {

	private static Reminder instance;

	public static Reminder getInstance() {
		if (instance == null) {
			instance = new Reminder();
		}
		return instance;
	}

	private ReminderThread thread;

	private LinkedList<ReminderListener> listeners;

	/**
	 * Task -> Calendar date to remind again
	 */
	private Hashtable<Task, Calendar> snoozedTasks;

	private Reminder() {
		// initi the reminder engine
		listeners = new LinkedList<ReminderListener>();
		snoozedTasks = new Hashtable<Task, Calendar>();

		// register in to task provider factory to kep update of newly
		// instanciated task providers
		TaskProviderFactory.getInstance().addFactoryListener(this);

		// initialize the task providers monitored when starting
		taskProvidersToMonitor = TaskProviderFactory.getInstance()
				.getTaskProviders();

		thread = new ReminderThread();
		thread.start();
	}

	public synchronized void addListener(ReminderListener listener) {
		listeners.add(listener);
	}

	public synchronized void removeListener(ReminderListener listener) {
		listeners.remove(listener);
	}

	protected void fireTaskToRemind(Task task) {
		Iterator<ReminderListener> itR = listeners.iterator();
		while (itR.hasNext()) {
			itR.next().remind(task);
		}
	}

	class ReminderThread extends Thread {
		public void run() {
			for (;;) {

				Calendar c = Calendar.getInstance();

				synchronized (Reminder.this) {
					Iterator<TaskProvider> itTP = taskProvidersToMonitor
							.iterator();
					while (itTP.hasNext()) {
						Iterator<Task> itT;
						try {
							itT = itTP.next().getTaskList().iterator();
							while (itT.hasNext()) {
								Task t = itT.next();

								if (instance.snoozedTasks.containsKey(t)) {
									Calendar snoozedUntil = instance.snoozedTasks
											.get(t);

									if (c.after(snoozedUntil)) {

										// remove the task from snoozed (to
										// ensure
										// GC is possible)
										snoozedTasks.remove(t);

										fireTaskToRemind(t);
									}
								} else if (t.needReminding(c)) {
									fireTaskToRemind(t);
								}
							}
						} catch (IOException e) {
							e.printStackTrace();

							// ignore
						}
					}
				}

				try {
					thread.sleep(5000);
				} catch (InterruptedException e) {

				}
			}
		}
	}

	private LinkedList<TaskProvider> taskProvidersToMonitor;

	public synchronized void taskProviderAdded(TaskProvider taskProvider) {
		taskProvidersToMonitor = TaskProviderFactory.getInstance()
				.getTaskProviders();
		
		// set as listener of the newly created provider
		taskProvider.addTaskProviderListener(this);

		// when provider added, then immediately check its task to remind by
		// interrupting the reminder thread
		thread.interrupt();
	}

	public synchronized void taskProviderRemoved(TaskProvider taskProvider) {
		// removed snoozed tasks from the removed provider
		try {
			Iterator<Task> tasks = taskProvider.getTaskList().iterator();
			while (tasks.hasNext()) {
				// no problem if taks is not snoozed, remove will be aborded
				snoozedTasks.remove(tasks.next());
			}
		} catch (Exception e) {
			// TODO log reminder error
		}

		taskProvidersToMonitor = TaskProviderFactory.getInstance()
				.getTaskProviders();
	}

	public synchronized void snoozeTask(Task taskToSnooze, int snoozeDelaySecond) {
		Calendar snoozedUntil = Calendar.getInstance();
		snoozedUntil.add(Calendar.SECOND, snoozeDelaySecond);
		snoozedTasks.put(taskToSnooze, snoozedUntil);
	}

	public synchronized void taskProviderUpdated(TaskProvider taskProvider) {
		// TODO remove tasks linked to this provider and not anymore in this
		// provider task list.
		// => remove desynchronizations
		Enumeration<Task> snoozedTasksEnum = snoozedTasks.keys();
		while (snoozedTasksEnum.hasMoreElements()) {
			Task task = snoozedTasksEnum.nextElement();
			if (task.getTaskProvider().equals(taskProvider)) {
				try {
					if (!taskProvider.getTaskList().contains(task)) {
						snoozedTasks.remove(task);
					}
				} catch (IOException e) {
					// TODO log reminder error
				}
			}
		}
	}
}
